package com.senlingspace.payment.core.refund;

import com.alibaba.fastjson2.JSON;
import com.senlingspace.common.core.dingtalk.DingTalkService;
import com.senlingspace.common.core.domain.AjaxResult;
import com.senlingspace.common.enums.OrderBusinessStatus;
import com.senlingspace.common.enums.OrderType;
import com.senlingspace.common.utils.DateUtils;
import com.senlingspace.common.utils.NumberGenerator;
import com.senlingspace.common.utils.bean.BeanUtils;
import com.senlingspace.common.enums.LakalaPaymentTypeEnum;
import com.senlingspace.common.enums.LakalaResponseCodeEnum;
import com.senlingspace.common.enums.LakalaTradeStateEnum;
import com.senlingspace.payment.lakala.bean.request.LakalaRefundRequest;
import com.senlingspace.payment.lakala.bean.response.LakalaRefundResponse;
import com.senlingspace.payment.lakala.service.LakalaRefundService;
import com.senlingspace.system.domain.SlkjOrder;
import com.senlingspace.system.domain.SlkjOrderLakal;
import com.senlingspace.system.domain.SlkjOrderStatusLog;
import com.senlingspace.system.domain.SlkjRefundRecord;
import com.senlingspace.system.domain.dto.RefundOrderDto;
import com.senlingspace.system.event.ActionEvent;
import com.senlingspace.system.mapper.SlkjOrderMapper;
import com.senlingspace.system.mapper.SlkjOrderStatusLogMapper;
import com.senlingspace.system.mapper.SlkjRefundRecordMapper;
import com.senlingspace.system.service.ISlkjOrderLakalService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 门店退款策略
 *
 * @author lebronjames
 * @date 2024/07/27
 */
@Slf4j
@AllArgsConstructor
@Service("shopRefundStrategy")
public class ShopRefundStrategy implements RefundStrategy {

    private final SlkjOrderMapper slkjOrderMapper;
    private final SlkjRefundRecordMapper slkjRefundRecordMapper;
    private final ISlkjOrderLakalService iSlkjOrderLakalService;
    private final LakalaRefundService lakalaRefundService;
    private final SlkjOrderStatusLogMapper slkjOrderStatusLogMapper;
    private final DingTalkService dingTalkService;

    private final ApplicationEventPublisher applicationEventPublisher;


    @Transactional(rollbackFor = Exception.class)
    @Override
    public AjaxResult refund(SlkjOrder order, Long operatorId) {
        log.info("门店退款: {}", order);

        String paymentStatus = order.getPaymentStatus();
        if (LakalaTradeStateEnum.REFUND.name().equals(paymentStatus)) {
            log.info("订单 [{}] 已退款，禁止重复退款！", order.getOrderNumber());
            return AjaxResult.error("订单已退款，禁止重复退款！");
        }

        if (LakalaTradeStateEnum.SUCCESS.name().equals(paymentStatus)) {
            String refundNumber = NumberGenerator.getCharacters(20);
            String paymentType = order.getPaymentType();
            // 现金支付
            if (LakalaPaymentTypeEnum.INCASH.name().equals(paymentType)) {
                log.info("现金退款...");
                SlkjOrder updateOrder = getUpdateOrder(order, operatorId);
                log.info("更新订单状态：{}", JSON.toJSONString(updateOrder));
                int update = slkjOrderMapper.updateSlkjOrder(updateOrder);

                SlkjOrderStatusLog statusLog = getUpdateOrderStatusLog(order, operatorId);
                log.info("更新订单状态日志：{}", JSON.toJSONString(statusLog));
                slkjOrderStatusLogMapper.insert(statusLog);

                SlkjRefundRecord refundRecord = new SlkjRefundRecord();
                refundRecord.setOrderId(order.getOrderId());
                refundRecord.setRefundNumber(refundNumber);
                refundRecord.setRefundAmount(order.getActualAmount());
                refundRecord.setRefundMethod("现金");
                refundRecord.setRefundStatus("REFUNDED");
                refundRecord.setRefundReason("门店现金退款！");
                refundRecord.setThirdRefundNumber(null);
                refundRecord.setRefundBy(operatorId);
                refundRecord.setRefundTime(updateOrder.getUpdateTime());
                refundRecord.setAdditionalInfo(null);
                refundRecord.setUpdateBy(null);
                refundRecord.setUpdateTime(null);
                log.info("插入退款记录：{}", JSON.toJSONString(refundRecord));
                int inserted = slkjRefundRecordMapper.insertSlkjRefundRecord(refundRecord);
                if (update == 0 || inserted == 0) {
                    log.error("退单数据插入失败，数据回滚：updateOrder={}, refundRecord={}", JSON.toJSONString(updateOrder), JSON.toJSONString(refundRecord));
                    throw new RuntimeException("系统异常，请刷新重试！");
                }
                log.info("订单 [{}] 退款成功！", order.getOrderNumber());

                log.info("退款事件发布：{}", order);
                RefundOrderDto orderDto = new RefundOrderDto();
                BeanUtils.copyProperties(order, orderDto);
                applicationEventPublisher.publishEvent(ActionEvent.of(orderDto, "现金退款-回退分账"));
                return AjaxResult.success("退款成功！");
            }

            // 拉卡拉支付
            log.info("拉卡拉退款...");
            SlkjOrderLakal orderLakal = iSlkjOrderLakalService.queryByOrderId(order.getOrderId());
            LakalaRefundRequest refundRequest = new LakalaRefundRequest();
            refundRequest.setLogNo(orderLakal.getLogNo());
            refundRequest.setOutTradeNo(refundNumber);
            // 对应拉卡拉 acc_settle_amount 账户端结算金额
            refundRequest.setRefundAmount(order.getActualAmount() + "");
            refundRequest.setRefundReason("门店操作退款！");
            refundRequest.setShopId(order.getShopId());

            log.info("调用拉卡拉执行退款：{}", JSON.toJSONString(refundRequest));
            LakalaRefundResponse refundResponse = lakalaRefundService.refund(refundRequest);
            log.info("退款请求响应：{}", JSON.toJSONString(refundResponse));
            if (LakalaResponseCodeEnum.BBS00000.name().equals(refundResponse.getCode())) {
                log.info("订单 [{}] 退款成功！", order.getOrderNumber());

                SlkjOrder updateOrder = getUpdateOrder(order, operatorId);
                log.info("更新订单状态：{}", JSON.toJSONString(updateOrder));
                int update = slkjOrderMapper.updateSlkjOrder(updateOrder);

                SlkjOrderStatusLog statusLog = getUpdateOrderStatusLog(order, operatorId);
                log.info("更新订单状态日志：{}", JSON.toJSONString(statusLog));
                slkjOrderStatusLogMapper.insert(statusLog);

                LakalaRefundResponse.RespData respData = refundResponse.getRespData();
                SlkjRefundRecord refundRecord = getSlkjRefundRecord(order, operatorId, refundNumber, respData, updateOrder);
                log.info("插入退款记录：{}", JSON.toJSONString(refundRecord));
                int inserted = slkjRefundRecordMapper.insertSlkjRefundRecord(refundRecord);
                if (update == 0 || inserted == 0) {
                    log.error("退单数据插入失败！：updateOrder={}, refundRecord={}", JSON.toJSONString(updateOrder), JSON.toJSONString(refundRecord));
                    log.error("这里需要手动处理数据，做数据补偿！！！");
                    dingTalkService.sendTextMessage("退单数据插入失败，参数信息：updateOrder=" + JSON.toJSONString(updateOrder) +
                            "，refundRecord=" + JSON.toJSONString(refundRecord) + "，需要将这些数据做手动补偿！", true);
                }

                log.info("门店退款事件发布：{}", order);
                RefundOrderDto orderDto = new RefundOrderDto();
                BeanUtils.copyProperties(order, orderDto);
                applicationEventPublisher.publishEvent(ActionEvent.of(orderDto, "线上退款-回退分账"));
                return AjaxResult.success("退款成功！");
            }

            log.error("订单 [{}] 退款失败：{}", order.getOrderNumber(), refundResponse.getCode() + ":" + refundResponse.getMsg());
            dingTalkService.sendTextMessage("订单[order_id=" + order.getOrderId() + "]退款失败！", true);
            return AjaxResult.error("退款失败（" + refundResponse.getCode() + "）!");
        }

        log.info("订单 [{}] 支付状态异常，非支付成功状态，禁止退款！", order.getOrderNumber());
        dingTalkService.sendTextMessage("订单[order_id=" + order.getOrderId() + "]支付状态异常，禁止退款！", true);
        return AjaxResult.error("订单支付状态异常，禁止退款！");
    }

    private static SlkjRefundRecord getSlkjRefundRecord(SlkjOrder order, Long operatorId, String refundNumber, LakalaRefundResponse.RespData respData, SlkjOrder updateOrder) {
        SlkjRefundRecord refundRecord = new SlkjRefundRecord();
        refundRecord.setOrderId(order.getOrderId());
        refundRecord.setRefundNumber(refundNumber);
        refundRecord.setRefundAmount(order.getActualAmount());
        refundRecord.setRefundMethod("原路退回");
        refundRecord.setRefundStatus("REFUNDED");
        refundRecord.setRefundReason("门店线上退款！");
        refundRecord.setThirdRefundNumber(respData.getLogNo());
        refundRecord.setRefundBy(operatorId);
        refundRecord.setRefundTime(updateOrder.getUpdateTime());
        refundRecord.setAdditionalInfo(JSON.toJSONString(respData));
        refundRecord.setUpdateBy(null);
        refundRecord.setUpdateTime(null);
        return refundRecord;
    }

    private static SlkjOrderStatusLog getUpdateOrderStatusLog(SlkjOrder order, Long operatorId) {
        SlkjOrderStatusLog orderStatusLog = new SlkjOrderStatusLog();
        orderStatusLog.setOrderId(order.getOrderId());
        orderStatusLog.setChangeBy(operatorId);
        orderStatusLog.setChangeTime(DateUtils.getNowDate());
        orderStatusLog.setStatus(OrderBusinessStatus.REFUNDED.getCode());
        return orderStatusLog;
    }

    private static SlkjOrder getUpdateOrder(SlkjOrder order, Long operatorId) {
        SlkjOrder updateOrder = new SlkjOrder();
        updateOrder.setOrderId(order.getOrderId());
        updateOrder.setPaymentStatus(LakalaTradeStateEnum.REFUND.name());
        updateOrder.setUpdateBy(operatorId + "");
        updateOrder.setUpdateTime(DateUtils.getNowDate());
        switch (OrderType.fromCode(order.getOrderType())) {
            case OT_101:
                updateOrder.setBusinessStatus(OrderBusinessStatus.REFUNDED.getCode());
                break;
            case OT_102:
                updateOrder.setGoodsStatus(OrderBusinessStatus.REFUNDED.getCode());
                break;
            case OT_103:
                updateOrder.setBusinessStatus(OrderBusinessStatus.REFUNDED.getCode());
                updateOrder.setGoodsStatus(OrderBusinessStatus.REFUNDED.getCode());
        }
        if (!order.getOrderType().equals(OrderType.OT_102.getCode())) {
            updateOrder.setFeeAmount(0);
            updateOrder.setSettleAmount(0);
        }
        return updateOrder;
    }

}